using System;
using System.Collections.Generic;
using System.Configuration;
using System.Configuration.Provider;
using System.Linq;
using System.Web.Configuration;

namespace NSecurity
{
    public delegate void ChangedEventHandler<TSecurable, TVerb, TSubject>(AccessControlEntry entry);

    // http://msdn.microsoft.com/en-us/library/aa479038.aspx

    public class AccessControlService<TSecurable, TVerb, TSubject>
        where TSecurable : class
        where TVerb : class
        where TSubject : class
    {
        private static readonly object Lock = new object();
        private static AccessControlService<TSecurable, TVerb, TSubject> instance;
        private volatile static AccessControlProviderBase<TSecurable, TVerb, TSubject> provider;
        private static AccessControlProviderCollection<TSecurable, TVerb, TSubject> providers;

        public static event ChangedEventHandler<TSecurable, TVerb, TSubject> Changed;

        public AccessControlService()
        {
            AccessControlService<TSecurable, TVerb, TSubject>.SectionName = "nsecurity";
            AccessControlService<TSecurable, TVerb, TSubject>.LoadProviders();
        }

        public static AccessControlService<TSecurable, TVerb, TSubject> Instance
        {
            get
            {
                return AccessControlService<TSecurable, TVerb, TSubject>.instance
                    ?? (AccessControlService<TSecurable, TVerb, TSubject>.instance = new AccessControlService<TSecurable, TVerb, TSubject>());
            }
        }

        public string PathDelimiter { get; set; }

        public static string SectionName { get; set; }

        public Func<TSecurable, TSecurable> GetAncestor
        {
            protected get;
            set;
        }

        public Func<TSubject, IEnumerable<TSubject>> GetRoles
        {
            protected get;
            set;
        }

        public AccessControlProviderBase<TSecurable, TVerb, TSubject> Provider
        {
            get { return AccessControlService<TSecurable, TVerb, TSubject>.provider; }
        }

        public AccessControlProviderCollection<TSecurable, TVerb, TSubject> Providers
        {
            get { return AccessControlService<TSecurable, TVerb, TSubject>.providers; }
        }

        public void Allow(TSecurable securable, TVerb verb, TSubject subject)
        {
            var entry = AccessControlService<TSecurable, TVerb, TSubject>.Instance.Provider.AddAccessControlEntry(securable, verb, subject, AccessType.Allow);
            if (AccessControlService<TSecurable, TVerb, TSubject>.Changed != null)
            {
                AccessControlService<TSecurable, TVerb, TSubject>.Changed(entry);
            }
        }

        public void ClearAccessControlEntries()
        {
            AccessControlService<TSecurable, TVerb, TSubject>.Instance.Provider.ClearAccessControlEntries();
        }

        public void Deny(TSecurable securable, TVerb verb, TSubject subject)
        {
            var entry = AccessControlService<TSecurable, TVerb, TSubject>.Instance.Provider.AddAccessControlEntry(securable, verb, subject, AccessType.Deny);
            if (AccessControlService<TSecurable, TVerb, TSubject>.Changed != null)
            {
                AccessControlService<TSecurable, TVerb, TSubject>.Changed(entry);
            }
        }

        public bool IsAllowed(TSecurable securable, TVerb verb, TSubject subject)
        {
            var entries = this.GetAccessControlEnties(securable, verb, subject).ToArray();
            var result = entries.Length != 0;
            foreach (var entry in entries)
            {
                result = entry.AccessType == AccessType.Allow;
                if (result == false)
                {
                    break;
                }
            }

            return result;
        }

        private IEnumerable<AccessControlEntry> GetAccessControlEnties(TSecurable securable, TVerb verb, TSubject subject)
        {
            var subjects = new List<TSubject> { subject };
            if (this.GetRoles != null)
            {
                var roles = this.GetRoles(subject);
                subjects.AddRange(roles);
            }

            var accessControlEntries = this.GetAccessControlEntriesInternal(securable, verb, subjects);
            return accessControlEntries;
        }

        private IEnumerable<AccessControlEntry> GetAccessControlEntriesInternal(TSecurable securable, TVerb verb, IEnumerable<TSubject> subjects)
        {
            var subjectList = subjects as TSubject[] ?? subjects.ToArray();
            var entries = AccessControlService<TSecurable, TVerb, TSubject>.Instance.Provider.FindAllAccessControlEntries(securable, verb, subjectList);
            var accessControlEntries = entries as AccessControlEntry[] ?? entries.ToArray();
            if (accessControlEntries.Any() == false && this.GetAncestor != null)
            {
                var parent = this.GetAncestor(securable);
                if (parent != default(TSecurable))
                {
                    entries = this.GetAccessControlEntriesInternal(parent, verb, subjectList);
                    accessControlEntries = entries as AccessControlEntry[] ?? entries.ToArray();
                }
            }

            return accessControlEntries;
        }

        private static void LoadProviders()
        {
            if (AccessControlService<TSecurable, TVerb, TSubject>.provider == null)
            {
                lock (AccessControlService<TSecurable, TVerb, TSubject>.Lock)
                {
                    if (AccessControlService<TSecurable, TVerb, TSubject>.provider == null)
                    {
                        var section = (AccessControlServiceSection)ConfigurationManager.GetSection(AccessControlService<TSecurable, TVerb, TSubject>.SectionName);
                        AccessControlService<TSecurable, TVerb, TSubject>.providers = new AccessControlProviderCollection<TSecurable, TVerb, TSubject>();
                        ProvidersHelper.InstantiateProviders(section.Providers
                            , AccessControlService<TSecurable, TVerb, TSubject>.providers
                            , typeof(AccessControlProviderBase<TSecurable, TVerb, TSubject>));
                        AccessControlService<TSecurable, TVerb, TSubject>.provider = AccessControlService<TSecurable, TVerb, TSubject>.providers[section.DefaultProvider];
                    }
                }
            }

            if (AccessControlService<TSecurable, TVerb, TSubject>.provider == null)
            {
                throw new ProviderException("Unable to load default provider");
            }
        }
    }
}